﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Platform.Linq
{

	// TODO : Realizar vários testes com esse objeto, principalmente na parte assíncrona
	internal sealed class MapReduceBag<TKey, TEmit> : IMapReduceBag<TEmit>, IEnumerable<TEmit>
	{
		private IDictionary<TKey, BagBucket> bag;
		private Func<TEmit, TKey> keySelector;
		private ReducerFunction<TKey, TEmit> reducer;

		internal MapReduceBag( Func<TEmit, TKey> keySelector, ReducerFunction<TKey, TEmit> reducer, IEqualityComparer<TKey> comparer )
		{
			this.keySelector = keySelector;
			this.reducer = reducer;
			this.bag = new Dictionary<TKey, BagBucket>( comparer );
		}


        public MapReduceBagState State
        {
            get
            {
                switch ( this.bag.Count )
                {
                    case 0:
                        return MapReduceBagState.NotInitialized;
                    default:
                        return MapReduceBagState.Building;
                }
            }
        }
		
		#region IEnumerable<TEmit> Members

		public IEnumerator<TEmit> GetEnumerator()
		{
			return this.bag.Values.Select( b => b.Item ).GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		#endregion

		/// <summary>
		/// Mantém em memória os itens processados
		/// </summary>
		/// <param name="elements"></param>
		public void Merge( IEnumerable<TEmit> elements )
		{
			foreach ( var item in elements )
			{
				var key = this.keySelector( item );
				BagBucket bucket = null;

				// Vai alterar o dicionário com os items já processados
				lock ( this.bag )
				{
					if ( this.bag.ContainsKey( key ) )
					{
						// Obtém o item que já existe, para realizar o reduce novamente
						bucket = this.bag[ key ];
						Monitor.Enter( bucket.SyncObject );
					}
					else
					{
						// Se não existe, simplesmente adiciona um novo
						this.bag.Add( key, new BagBucket( item ) );
					}
				}

				// Se já existe um outro item processado, então 
				// já obteu o outro item e o acesso exclusivo para esse item
				// podendo fazer o processamento sem problemas
				if ( bucket != null )
				{
					try
					{
						bucket.Item = this.reducer.Compiled( new TwoReducedItemsGrouping( key, bucket.Item, item ) );
					}
					finally
					{
						Monitor.Exit( bucket.SyncObject );
					}
				}
			}
		}

		internal class BagBucket
		{
			public BagBucket( TEmit item )
			{
				this.Item = item;
				this.SyncObject = new object();
			}

			public TEmit Item
			{ get; set; }

			public object SyncObject
			{ get; set; }

		}

		internal sealed class TwoReducedItemsGrouping : IGrouping<TKey, TEmit>
		{
			private TKey key;
			private TEmit item1;
			private TEmit item2;


			public TwoReducedItemsGrouping( TKey key, TEmit item1, TEmit item2 )
			{
				this.key = key;
				this.item1 = item1;
				this.item2 = item2;
			}

			#region IGrouping<TKey,TEmit> Members

			public TKey Key
			{
				get { return this.key; }
			}

			#endregion

			#region IEnumerable<TEmit> Members

			public IEnumerator<TEmit> GetEnumerator()
			{
				yield return this.item1;
				yield return this.item2;
			}

			#endregion

			#region IEnumerable Members

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return this.GetEnumerator();
			}

			#endregion
		}
	}

	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="TEmit"></typeparam>
	public interface IMapReduceBag<TEmit> : IEnumerable<TEmit>
	{
		/// <summary>
		/// Indica se a bag já foi inicializada com algum valor
		/// </summary>
		//bool Initialized
		//{ get; }

		/// <summary>
		/// Obtém ou define o último registro processado
		/// </summary>
		//string LastProcess
		//{ get; set; }

        /// <summary>
        /// Indica se já houve pelo menos um item processado
        /// </summary>
        MapReduceBagState State
        { get; }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="elements"></param>
		void Merge( IEnumerable<TEmit> elements );
	}

    public enum MapReduceBagState
    {
        NotInitialized = 0,
        Building = 1
    }

	internal sealed class DocumentMapReduceBag<TKey, TEmit> : IMapReduceBag<TEmit>
	{

		#region IMapReduceBag<TEmit> Members

		public void Merge( IEnumerable<TEmit> elements )
		{
			throw new NotImplementedException();
		}

		#endregion

		#region IEnumerable<TEmit> Members

		public IEnumerator<TEmit> GetEnumerator()
		{
			throw new NotImplementedException();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			throw new NotImplementedException();
		}

		#endregion

		#region IMapReduceBag<TEmit> Members

		public bool Initialized
		{
			// Verifica no banco de possui algo
			get { throw new NotImplementedException(); }
		}

		#endregion

        public MapReduceBagState State
        {
            get { throw new NotImplementedException(); }
        }
    }


	internal sealed class EntityMapReduceBag<TKey, TEmit> : IMapReduceBag<TEmit>
	{
		#region IMapReduceBag<TEmit> Members

		public void Merge( IEnumerable<TEmit> elements )
		{
			// Se for uma query, já pode transformar no SQL que será utilizado para fazer a consulta
			// e montar o INSERT/UPDATE/MERGE

			throw new NotImplementedException();
		}

		#endregion

		#region IEnumerable<TEmit> Members

		public IEnumerator<TEmit> GetEnumerator()
		{
			throw new NotImplementedException();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			throw new NotImplementedException();
		}

		#endregion

		#region IMapReduceBag<TEmit> Members

		public bool Initialized
		{
			get { throw new NotImplementedException(); }
		}

		#endregion

        public MapReduceBagState State
        {
            get { throw new NotImplementedException(); }
        }
    }
}
